﻿/*
Copyright 2010 Shane Ng

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.DirectoryServices;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Xml;
using System.Xml.Linq;
using javax.naming;
using javax.servlet;
using Microsoft.Practices.Unity;
using Microsoft.Win32;
using org.apache.naming.java;
using Microsoft.Practices.Unity.Configuration;
using System.Configuration;

namespace Jeans
{
    public class ServletContextAdapter : ServletContext
    {
        public static ServletContext Instance {
            get;
            private set;
        }
        public static void Init(HttpContext context, params TextReader[] readers){
            Instance = new ServletContextAdapter(context, readers);
        }

        private HttpContext context;
        private XElement webxml;
        private Dictionary<string, Servlet> servletDefinition = new Dictionary<string, Servlet>();
        private List<MappingEntry<Servlet>> servletMapping = new List<MappingEntry<Servlet>>();
        private Dictionary<string, Filter> filterDefinition = new Dictionary<string, Filter>();
        private List<MappingEntry<Filter>> filterMapping = new List<MappingEntry<Filter>>();

        private ServletContextAdapter(HttpContext context, params TextReader[] readers) {
            this.context = context;
            PrepareWebXml(readers);
            
            CreateDefinition("servlet", servletDefinition, e => new ServletConfigAdapter(this, e), (s, c)=>s.init(c));
            CreateMapping("servlet", servletMapping, (p, n) => new MappingEntry<Servlet>(p, servletDefinition[n]));
            CreateDefinition("filter", filterDefinition, e => new FilterConfigAdapter(this, e), (s, c) => s.init(c));
            CreateMapping("filter", filterMapping, (p, n) => new MappingEntry<Filter>(p, filterDefinition[n]));

            java.lang.System.setProperty(Context.__Fields.INITIAL_CONTEXT_FACTORY, typeof(javaURLContextFactory).FullName);
            Context ctx = new InitialContext();
            Context javaCtx = ctx.createSubcontext("jdbc");
            ///// snippet may be useful for further JNDI implementation
            // Context javaCtx = ctx.createSubcontext("java");
            // Context jdbcCtx = javaCtx.createSubcontext("comp").createSubcontext("env").createSubcontext("jdbc");
            // ctx.bind("java:", javaCtx);
            /////
            var section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
            var container = new UnityContainer();
            section.Configure(container);
            
            foreach (var element in webxml.Elements("resource-ref")) {
                string resType = element.Element("res-type").Value;
                if (resType == "javax.sql.DataSource") {
                    string refName = element.Element("res-ref-name").Value;
                    var resource = container.Resolve<javax.sql.DataSource>(refName);
                    javaCtx.bind(refName.Substring(refName.IndexOf('/') + 1), resource);
                    ///// statement may be useful for further JNDI implementation
                    // jdbcCtx.bind(refName.Substring(refName.IndexOf('/') + 1), resource);
                    /////
                }
            }
        }

        private void PrepareWebXml(TextReader[] readers) {
            // Remove the namespace
            using (StringWriter writer = new StringWriter()) {
                using (var reader = readers[0]) {
                    writer.Write(reader.ReadToEnd().Replace(@"xmlns=""http://java.sun.com/xml/ns/j2ee""", ""));
                    writer.Flush();
                    this.webxml = XElement.Parse(writer.ToString());
                }
            }

            // Add JSP Servlet definition and mappings
            if (readers.Length > 1) {
                foreach (var reader in readers) {
                    if (reader == readers.First()) {
                        continue;
                    }
                    try {
                        var xml = XElement.Load(reader);
                        AddElement(xml, "servlet");
                        AddElement(xml, "servlet-mapping");
                    }
                    finally {
                        reader.Dispose();
                    }
                }
            }
        }

        private void CreateDefinition<T, U>(string configPrefix, Dictionary<string, T> targetDefinitions, Func<XElement, U> createConfig, Action<T, U> initAction) where U : BaseServletConfig {
            foreach (var element in webxml.Elements(configPrefix)) {
                U config = createConfig(element);
                T instance = default(T);
                string className = element.Element(configPrefix + "-class").Value;
                Type t = Type.GetType(Assembly.CreateQualifiedName("jsp", className));
                if (t != null) {
                    instance = (T)t.GetConstructor(new Type[] { }).Invoke(null);
                }
                else {
                    instance = (T)java.lang.Thread.currentThread().getContextClassLoader().loadClass(className).newInstance();
                }
                initAction(instance, config);
                targetDefinitions.Add(config.getName(), instance);
            }
        }

        private void CreateMapping<T>(string configPrefix, List<T> mappings, Func<string, string, T> createMapping) {
            foreach (var element in webxml.Elements(configPrefix + "-mapping")) {
                string pattern = element.Element("url-pattern").Value;
                string name = element.Element(configPrefix + "-name").Value;
                T mapping = createMapping(pattern, name);
                mappings.Add(mapping);
            }
        }

        private void AddElement(XElement xml, string element) {
            if (this.webxml.Elements(element).Any()) {
                this.webxml.Elements(element).Last().AddAfterSelf(xml.Elements(element));
            }
            else {
                this.webxml.Add(xml.Elements(element));
            }
        }

        public List<Filter> getFilters(string url) {
            return filterMapping.Where(e => e.Matches(url)).Select(o => o.Value).ToList();
        }

        public object getAttribute(string str) {
            return context.Application[str];
        }

        public java.util.Enumeration getAttributeNames() {
            return new JavaEnumerator<string>(context.Application.AllKeys);
        }

        public ServletContext getContext(string str) {
            throw new NotImplementedException();
        }

        public string getInitParameter(string str) {
            return webxml
                         .Elements("context-param")
                         .SingleOrDefault(e => e.Element("param-name").Value == str)
                         .ReturnOrDefault(e => e.Element("param-value").Value);
        }

        public java.util.Enumeration getInitParameterNames() {
            return new JavaEnumerator<string>(webxml.Elements("context-param").Elements("param-name").Select(e => e.Value));
        }

        public int getMajorVersion() {
            return 2;
        }

        public string getMimeType(string extension) {
            using (DirectoryEntry mimeMap = new DirectoryEntry("IIS://Localhost/MimeMap")) {
                PropertyValueCollection propValues = mimeMap.Properties["MimeMap"];
                foreach (object value in propValues) {
                    IISOle.IISMimeType mimeType = (IISOle.IISMimeType)value;

                    if (extension == mimeType.Extension) {
                        return mimeType.MimeType;
                    }
                }
                return null;
            }
        }

        public int getMinorVersion() {
            return 4;
        }

        public RequestDispatcher getNamedDispatcher(string str) {
            if (servletDefinition.ContainsKey(str)) {
                return new ServletRequestDispatcher(servletDefinition[str]);
            }
            else {
                return null;
            }
        }

        public string getRealPath(string str) {
            return context.ApplicationInstance.Server.MapPath(str);
        }

        public RequestDispatcher getRequestDispatcher(string url) {
            return servletMapping.SingleOrDefault(e => e.Matches(url))
                                 .ReturnOrDefault(e => new ServletRequestDispatcher(e.Value));
        }

        public java.net.URL getResource(string str) {
            throw new NotImplementedException();
        }

        public java.io.InputStream getResourceAsStream(string str) {
            throw new NotImplementedException();
        }

        public java.util.Set getResourcePaths(string str) {
            throw new NotImplementedException();
        }

        public string getServerInfo() {
            return "ASP.NET";
        }

        public Servlet getServlet(string str) {
            return servletDefinition[str];
        }

        public string getServletContextName() {
            throw new NotImplementedException();
        }

        public java.util.Enumeration getServletNames() {
            return new JavaEnumerator<string>(servletDefinition.Keys);
        }

        public java.util.Enumeration getServlets() {
            return new JavaEnumerator<Servlet>(servletDefinition.Values);
        }

        public void log(java.lang.Exception e, string str) {
            throw new NotImplementedException();
        }

        public void log(string str) {
            throw new NotImplementedException();
        }

        public void log(string str, Exception t) {
            throw new NotImplementedException();
        }

        public void removeAttribute(string str) {
            context.Application.Remove(str);
        }

        public void setAttribute(string str, object obj) {
            context.Application[str] = obj;
        }
    }
}